/* Copyright 2007-2008 dnAnalytics Project.
 *
 * Contributors to this file:
 * Patrick van der Velde
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this 
 *   list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice, 
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the dnAnalytics Project nor the names of its contributors
 *   may be used to endorse or promote products derived from this software without
 *   specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Diagnostics;
using NUnit.Framework;
using dnAnalytics.LinearAlgebra.Solvers;
using System.Collections.Generic;
using dnAnalytics.LinearAlgebra;
using System.Reflection;

namespace dnAnalytics.Tests.LinearAlgebra.Solvers
{
    [TestFixture]
    public sealed class IteratorTest
    {
        private static T GetProperty<T>(Iterator iterator, string propertyName)
        {
            var type = iterator.GetType();
            var propertyInfo = type.GetProperty(propertyName,
                                                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static,
                                                null,
                                                typeof(T),
                                                new Type[0],
                                                null);
            var obj = propertyInfo.GetValue(iterator, null);
            return (T)obj;
        }

        private static int GetCriteriumCount(Iterator iterator)
        {
            return GetProperty<int>(iterator, "NumberOfCriteria");
        }

        private static IEnumerator<IIterationStopCriterium> GetCriteriumIterator(Iterator iterator)
        {
            return GetProperty<IEnumerator<IIterationStopCriterium>>(iterator, "StoredStopCriteria");
        }

        [Test]
        public void CreateWithNullCollection()
        {
            Iterator iterator = new Iterator(null);
            Assert.IsNotNull(iterator, "Should have an iterator");
            Assert.AreEqual(0, GetCriteriumCount(iterator), "There shouldn't be any criteria");
        }

        [Test]
        public void CreateWithEmptyCollection()
        {
            Iterator iterator = new Iterator(new IIterationStopCriterium[] { });
            Assert.IsNotNull(iterator, "Should have an iterator");
            Assert.AreEqual(0, GetCriteriumCount(iterator), "There shouldn't be any criteria");
        }

        [Test]
        public void CreateWithCollectionWithNulls()
        {
            Iterator iterator = new Iterator(new IIterationStopCriterium[] { null, null });
            Assert.IsNotNull(iterator, "Should have an iterator");
            Assert.AreEqual(0, GetCriteriumCount(iterator), "There shouldn't be any criteria");
        }

        [Test]
        [ExpectedException(typeof(DuplicateStopCriteriumException))]
        public void CreateWithDuplicates()
        {
            Iterator iterator = new Iterator(new IIterationStopCriterium[] { 
                                                                                new FailureStopCriterium(), 
                                                                                new FailureStopCriterium() 
                                                                            });
            Assert.IsNotNull(iterator, "Should have an iterator");
            Assert.AreEqual(1, GetCriteriumCount(iterator), "There should be one criterium");
        }

        [Test]
        public void CreateWithCollection()
        { 
            var criteria =  new List<IIterationStopCriterium>{ 
                                                                 new FailureStopCriterium(), 
                                                                 new DivergenceStopCriterium(),
                                                                 new IterationCountStopCriterium(),
                                                                 new ResidualStopCriterium()
                                                             };
            Iterator iterator = new Iterator(criteria);
            Assert.IsNotNull(iterator, "Should have an iterator");
            
            // Check that we have all the criteria
            Assert.AreEqual(criteria.Count, GetCriteriumCount(iterator), "Incorrect criterium count");
            IEnumerator<IIterationStopCriterium> enumerator = GetCriteriumIterator(iterator);
            while (enumerator.MoveNext())
            {
                var criterium = enumerator.Current;
                Assert.IsTrue(criteria.Exists((IIterationStopCriterium c) => ReferenceEquals(c, criterium)), "Criterium missing");
            }
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void AddWithNullStopCriterium()
        {
            Iterator iterator = new Iterator();
            iterator.Add(null);
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(DuplicateStopCriteriumException))]
        public void AddWithExistingStopCriterium()
        {
            Iterator iterator = new Iterator();
            iterator.Add(new FailureStopCriterium());
            Assert.AreEqual(1, GetCriteriumCount(iterator), "Incorrect criterium count");

            iterator.Add(new FailureStopCriterium());
            Assert.Fail();
        }

        [Test]
        public void Add()
        {
            var criteria = new List<IIterationStopCriterium>{ 
                                                                new FailureStopCriterium(), 
                                                                new DivergenceStopCriterium(),
                                                                new IterationCountStopCriterium(),
                                                                new ResidualStopCriterium()
                                                            };
            Iterator iterator = new Iterator();
            Assert.AreEqual(0, GetCriteriumCount(iterator), "Incorrect criterium count");

            foreach (var criterium in criteria)
            {
                iterator.Add(criterium);
                Assert.IsTrue(iterator.Contains(criterium), "Missing criterium");
            }

            // Check that we have all the criteria
            Assert.AreEqual(criteria.Count, GetCriteriumCount(iterator), "Incorrect criterium count");
            IEnumerator<IIterationStopCriterium> enumerator = GetCriteriumIterator(iterator);
            while (enumerator.MoveNext())
            {
                var criterium = enumerator.Current;
                Assert.IsTrue(criteria.Exists((IIterationStopCriterium c) => ReferenceEquals(c, criterium)), "Criterium missing");
            }
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void RemoveWithNullStopCriterium()
        {
            var criteria = new List<IIterationStopCriterium>{ 
                                                                new FailureStopCriterium(), 
                                                                new DivergenceStopCriterium(),
                                                                new IterationCountStopCriterium(),
                                                                new ResidualStopCriterium()
                                                            };
            Iterator iterator = new Iterator(criteria);
            Assert.AreEqual(criteria.Count, GetCriteriumCount(iterator), "Incorrect criterium count");

            iterator.Remove(null);
            Assert.Fail();
        }

        [Test]
        public void RemoveWithNonExistingStopCriterium()
        {
            var criteria = new List<IIterationStopCriterium>{ 
                                                                new FailureStopCriterium(), 
                                                                new DivergenceStopCriterium(),
                                                                new IterationCountStopCriterium(),
                                                            };
            Iterator iterator = new Iterator(criteria);
            Assert.AreEqual(criteria.Count, GetCriteriumCount(iterator), "Incorrect criterium count");

            iterator.Remove(new ResidualStopCriterium());
            Assert.AreEqual(criteria.Count, GetCriteriumCount(iterator), "Incorrect criterium count");
        }

        [Test]
        public void Remove()
        {
            var criteria = new List<IIterationStopCriterium>{ 
                                                                new FailureStopCriterium(), 
                                                                new DivergenceStopCriterium(),
                                                                new IterationCountStopCriterium(),
                                                                new ResidualStopCriterium()
                                                            };
            Iterator iterator = new Iterator(criteria);
            Assert.AreEqual(criteria.Count, GetCriteriumCount(iterator), "Incorrect criterium count");

            foreach (var criterium in criteria)
            {
                iterator.Remove(criterium);
                Assert.IsFalse(iterator.Contains(criterium), "Did not remove the criterium");
            }
        }

        [Test]
        [ExpectedException(typeof(StopCriteriumMissingException))]
        public void DetermineStatusWithoutStopCriteria()
        {
            Iterator iterator = new Iterator();
            iterator.DetermineStatus(0,
                                     new DenseVector(3, 4),
                                     new DenseVector(3, 5),
                                     new DenseVector(3, 6));
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void DetermineStatusWithNegativeIterationNumber()
        {
            var criteria = new List<IIterationStopCriterium>{ 
                                                                new FailureStopCriterium(), 
                                                                new DivergenceStopCriterium(),
                                                                new IterationCountStopCriterium(),
                                                                new ResidualStopCriterium()
                                                            };
            Iterator iterator = new Iterator(criteria);
            
            iterator.DetermineStatus(-1,
                                     new DenseVector(3, 4),
                                     new DenseVector(3, 5),
                                     new DenseVector(3, 6));
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void DetermineStatusWithNullSolutionVector()
        {
            var criteria = new List<IIterationStopCriterium>{ 
                                                                new FailureStopCriterium(), 
                                                                new DivergenceStopCriterium(),
                                                                new IterationCountStopCriterium(),
                                                                new ResidualStopCriterium()
                                                            };
            Iterator iterator = new Iterator(criteria);

            iterator.DetermineStatus(1,
                                     null,
                                     new DenseVector(3, 5),
                                     new DenseVector(3, 6));
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void DetermineStatusWithNullSourceVector()
        {
            var criteria = new List<IIterationStopCriterium>{ 
                                                                new FailureStopCriterium(), 
                                                                new DivergenceStopCriterium(),
                                                                new IterationCountStopCriterium(),
                                                                new ResidualStopCriterium()
                                                            };
            Iterator iterator = new Iterator(criteria);

            iterator.DetermineStatus(1,
                                     new DenseVector(3, 5),
                                     null,
                                     new DenseVector(3, 6));
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void DetermineStatusWithNullResidualVector()
        {
            var criteria = new List<IIterationStopCriterium>{ 
                                                                new FailureStopCriterium(), 
                                                                new DivergenceStopCriterium(),
                                                                new IterationCountStopCriterium(),
                                                                new ResidualStopCriterium()
                                                            };
            Iterator iterator = new Iterator(criteria);

            iterator.DetermineStatus(1,
                                     new DenseVector(3, 4),
                                     new DenseVector(3, 5),
                                     null);
            Assert.Fail();
        }

        [Test]
        public void DetermineStatus()
        {
            var criteria = new List<IIterationStopCriterium>{ 
                                                                new FailureStopCriterium(), 
                                                                new DivergenceStopCriterium(),
                                                                new IterationCountStopCriterium(1)
                                                            };
            Iterator iterator = new Iterator(criteria);
            // First step, nothing should happen.
            {
                iterator.DetermineStatus(0,
                                         new DenseVector(3, 4),
                                         new DenseVector(3, 4),
                                         new DenseVector(3, 4));
                Assert.IsInstanceOfType(typeof(CalculationRunning), iterator.Status, "Incorrect status"); 
            }

            // Second step, should run out of iterations.
            {
                iterator.DetermineStatus(1,
                                         new DenseVector(3, 4),
                                         new DenseVector(3, 4),
                                         new DenseVector(3, 4));
                Assert.IsInstanceOfType(typeof(CalculationStoppedWithoutConvergence), iterator.Status, "Incorrect status"); 
            }
        }

        [Test]
        public void ResetToPrecalculationState()
        {
            var criteria = new List<IIterationStopCriterium>{ 
                                                                new FailureStopCriterium(), 
                                                                new DivergenceStopCriterium(),
                                                                new IterationCountStopCriterium(1)
                                                            };
            Iterator iterator = new Iterator(criteria);
            // First step, nothing should happen.
            {
                iterator.DetermineStatus(0,
                                         new DenseVector(3, 4),
                                         new DenseVector(3, 4),
                                         new DenseVector(3, 4));
                Assert.IsInstanceOfType(typeof(CalculationRunning), iterator.Status, "Incorrect status");
            }

            iterator.ResetToPrecalculationState();
            Assert.IsInstanceOfType(typeof(CalculationIndetermined), iterator.Status, "Incorrect status");
            Assert.IsInstanceOfType(typeof(CalculationIndetermined), criteria[0].Status, "Incorrect status");
            Assert.IsInstanceOfType(typeof(CalculationIndetermined), criteria[1].Status, "Incorrect status");
            Assert.IsInstanceOfType(typeof(CalculationIndetermined), criteria[2].Status, "Incorrect status");
        }

        [Test]
        public void Clone()
        {
            var criteria = new List<IIterationStopCriterium>{ 
                                                                new FailureStopCriterium(), 
                                                                new DivergenceStopCriterium(),
                                                                new IterationCountStopCriterium(),
                                                                new ResidualStopCriterium()
                                                            };
            Iterator iterator = new Iterator(criteria);

            IIterator clonedIterator = iterator.Clone();
            Assert.IsInstanceOfType(typeof(Iterator), clonedIterator, "Incorrect type");

            Iterator clone = clonedIterator as Iterator;
            Assert.AreEqual(GetCriteriumCount(iterator), GetCriteriumCount(clone), "Incorrect criterium count");

            IEnumerator<IIterationStopCriterium> enumerator = GetCriteriumIterator(clone);
            while (enumerator.MoveNext())
            {
                var criterium = enumerator.Current;
                Assert.IsTrue(criteria.Exists((IIterationStopCriterium c) => c.GetType().Equals(criterium.GetType())), "Criterium missing");
            }
        }
    }
}
